﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Miner.Controllers.Interfaces;
using Miner.Model.Logic;
using Miner.Model.Logic.Enums;
using Miner.UI.Forms;
using Miner.UI.Forms.Interfaces;

namespace MinerMiner.Controllers
{
    class Game: IGame
    {
        #region Members
        private int xBlocksCount;
        private int yBlocksCount;
        private int blockSize;
        private int minesCount;
        private IGameForm frm;
        private int minesLeftToFind;
        private int blocksOpened;
        private int blocksToOpen;

        private Block[,] blocks = null;
        private List<Block> minesBlocks = null;
        private List<MPoint> minesPoints = null;

        private DrawingArea area;

        private Timer gameTimer;
        private int timeElapsed;
        private GameStatus status; 
        #endregion

        #region Ctor
        public Game(int blockSize)
        {
            this.blockSize = blockSize;

            Init();
        } 
        #endregion

        #region Initialization
        private void Init()
        {
            InitTimer();

            InitGameForm();

            InittNewGame();

            blocks = new Block[xBlocksCount, yBlocksCount];
            minesBlocks = new List<Block>();
            minesPoints = new List<MPoint>();



            frm.Freeze();

            InitBlocks();

            Status = GameStatus.Playing;

            frm.Display();
        }

        private void InitTimer()
        {
            this.gameTimer = new Timer();
            gameTimer.Interval = 100;
            timeElapsed = 0;
            gameTimer.Tick += delegate(Object o, EventArgs e)
                {
                    frm.GameInfoCtrl.TimeElapsedValue = getTimerString(timeElapsed++);
                };
        } 
        
        private void InitBlocks()
        {
            Func<MPoint, Block> getBlockNeibor = (p) =>
            {
                if (checkInArea(p))
                {
                    return blocks[p.x, p.y];
                }
                return null;
            };
            
            for (int x = 0; x < xBlocksCount; x++)
            {
                for (int y = 0; y < yBlocksCount; y++)
                {
                    MPoint p = new MPoint(x, y);
                    Block block = new Block
                    (
                        p,
                        blockSize,
                        getBlockNeibor(p.Left()),
                        getBlockNeibor(p.Down()),
                        getBlockNeibor(p.Right()),
                        getBlockNeibor(p.Up()),
                        area
                    );


                    block.Opened += (sender, e) =>
                    {
                        if (blocksOpened == 0)
                        {
                            InitMines();
                        }
                        blocksOpened++;
                        if (blocksToOpen == blocksOpened)
                        {
                            CheckGameResult();
                        }
                    };
                    block.Marked += (sender, e) =>
                    {
                        MinesLeftToFind--;
                        if (minesLeftToFind == 0)
                        {
                            CheckGameResult();
                        }
                    };
                    block.UnMarked += (sender, e) =>
                    {
                        MinesLeftToFind++;
                    };

                    blocks[x, y] = block;
                    area.DrawHiddenBlock(p);
                }
            }
        }
        
        private void InittNewGame()
        {
            #region InnerMethods
            Func<GameOptions, int> getMinesCount = (options) =>
                    {
                        return options.MinesCount >= (this.xBlocksCount * this.yBlocksCount) ? (this.xBlocksCount * this.yBlocksCount) * 8 / 10 : options.MinesCount;
                    }; 
            #endregion
            
            INewGameForm newGameForm = new NewGameForm();

            #region NewGameFormEventHandlers
            EventHandler ExitingHandler = (sender, e) => Environment.Exit(0);
            StartGameDelegate StartingHandler = (options) =>
                {
                this.xBlocksCount = options.xBlocksCount;
                this.yBlocksCount = options.yBlocksCount;
                this.minesCount = getMinesCount(options);
                this.minesLeftToFind = this.minesCount;
                this.blocksOpened = 0;
                this.blocksToOpen = this.xBlocksCount * this.yBlocksCount - this.minesCount;

                frm.DrawAreaCtrl.Init(options, frm.Width, frm.Height);
                frm.DrawAreaCtrl.BlockClicked += (sender, e) =>
                    {
                        int x = e.X/blockSize;
                        int y = e.Y/blockSize;

                        if (e.Button == MouseButtons.Right && !blocks[x,y].WasOpened)
                        {
                            if (minesLeftToFind!=0)
                            {
                                blocks[x, y].Mark(); 
                            }
                        }
                        else
                        {
                            blocks[x,y].Open();
                        }
                    };

                this.area = new DrawingArea(options, frm.DrawAreaCtrl, Application.StartupPath + @"\UI\Images");
                frm.GameInfoCtrl.MinesLeftValue = minesLeftToFind.ToString();
                frm.GameInfoCtrl.PauseClick+= (sender, e) =>
                    {
                        Status = Status==GameStatus.Playing ? GameStatus.Paused : GameStatus.Playing;
                    };
            }; 
            #endregion

            newGameForm.Exiting += ExitingHandler;
            newGameForm.Starting += StartingHandler;
            newGameForm.Run();
            newGameForm.Kill();
            newGameForm.Exiting -= ExitingHandler;
            newGameForm.Starting -= StartingHandler;
        }

        private void InitGameForm()
        {
            this.frm = new MainForm();
            this.frm.KeyUpOnForm += delegate(Object o, KeyEventArgs e)
            {
                if (e.KeyCode == Keys.Escape)
                {
                    Status = GameStatus.Paused;
                    switch (new ExitRestartPromtForm().ShowDialog())
                    {
                        case System.Windows.Forms.DialogResult.Abort:
                            break;

                        case System.Windows.Forms.DialogResult.Retry:
                            break;

                        case System.Windows.Forms.DialogResult.Cancel:
                            break;
                    }
                    Status = GameStatus.Playing;
                }
            };
        }
        
        private void InitMines()
        {
            #region InnerMethods
            Action<MPoint> SetMine = (p) =>
                    {
                        Action<MPoint> UpdateBlock = (point) =>
                            {
                                if (checkInArea(point) && !blocks[point.x, point.y].HasMine)
                                {
                                    blocks[point.x, point.y].IsEmpty = false;
                                    blocks[point.x, point.y].Value++;
                                }
                            };

                        Block mine = blocks[p.x, p.y];

                        minesPoints.Add(p);
                        minesBlocks.Add(mine);
                        mine.HasMine = true;
                        mine.Exploaded += block_Exploaded;

                        UpdateBlock(p.UpLeft());
                        UpdateBlock(p.Up());
                        UpdateBlock(p.UpRight());
                        UpdateBlock(p.Left());
                        UpdateBlock(p.Right());
                        UpdateBlock(p.DownLeft());
                        UpdateBlock(p.Down());
                        UpdateBlock(p.DownRight());
                    }; 
            #endregion
            
            Random rnd = new Random(DateTime.Now.Millisecond);

            while (minesPoints.Count < minesCount)
            {
                MPoint p = new MPoint(rnd.Next(0, xBlocksCount), rnd.Next(0, yBlocksCount));

                if (!minesPoints.Contains(p))
                {
                    SetMine(p);
                }
            }

            minesPoints = null;
        }
        #endregion

        #region Properties
        private int MinesLeftToFind
        {
            get{return minesLeftToFind;}
            set
            {
                minesLeftToFind = value;
                frm.GameInfoCtrl.TimeElapsedValue = minesLeftToFind.ToString();
            }
        }
        #endregion

        #region IGame
        public GameStatus Status
        {
            get { return status; }
            private set
            {
                switch (value)
                {
                    case GameStatus.Paused:
                        frm.GameInfoCtrl.ChangeSmile(SmileChangeReason.Pause);
                        frm.DrawAreaCtrl.Lock();
                        this.gameTimer.Stop();
                        break;
                    case GameStatus.Playing:
                        frm.GameInfoCtrl.ChangeSmile(SmileChangeReason.Resume);
                        frm.DrawAreaCtrl.UnLock();
                        this.gameTimer.Start();
                        break;
                    case GameStatus.Stoped:
                        frm.GameInfoCtrl.ChangeSmile(SmileChangeReason.Fail);
                        this.gameTimer.Stop();
                        break;
                }
                status = value;
            }
        }

        public void Start()
        {
            throw new NotImplementedException();
        }

        void IGame.Restart()
        {
            throw new NotImplementedException();
        }

        public void Pause()
        {
            throw new NotImplementedException();
        }

        public void Resume()
        {
            throw new NotImplementedException();
        }

        public void End()
        {
            throw new NotImplementedException();
        } 
        #endregion

        #region Tools
        private void block_Exploaded(object sender, EventArgs e)
        {
            frm.GameInfoCtrl.ChangeSmile(SmileChangeReason.Fail);
            GameOver(false);
        }

        private bool checkInArea(MPoint p)
        {
            return p.x >= 0 && p.x < xBlocksCount && p.y >= 0 && p.y < yBlocksCount;
        }

        private void GameOver(bool victory)
        {
            Status = GameStatus.Stoped;
            
            if (!victory)
            {
                foreach (var block in minesBlocks)
                {
                    block.Exploaded -= block_Exploaded;
                    block.Open();
                }
                MessageBox.Show("GAME OVER");
            }
            else
            {
                MessageBox.Show("YOU WON!!!");
            }

            Restart();
        }

        private void CheckGameResult()
        {
            if (xBlocksCount * yBlocksCount - minesCount == blocksOpened && minesLeftToFind <= 0)
            {
                GameOver(true);
            }
        }

        private string getTimerString(int miliseconds)
        {
            int mins = miliseconds/10/60;
            int secs = miliseconds/10;
            return string.Format("{0:00}:{1:00}:{2:00}", mins, secs - mins*60, miliseconds-secs*10);
        }
        
        private void Restart()
        {
            blocks = null;
            (frm as Form).Dispose();

            minesLeftToFind = minesCount;
            blocksOpened = 0;

            Init();
        }
        #endregion
    }

    #region SuppotringClasses&Extensions
    internal class MPoint
    {
        public int x;
        public int y;

        public MPoint(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        public override bool Equals(object obj)
        {
            MPoint temp = obj as MPoint;
            if (temp != null && temp.x == this.x && temp.y == this.y)
            {
                return true;
            }
            return false;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    internal static class MinePlaceExtentions
    {
        public static MPoint UpLeft(this MPoint place)
        {
            return new MPoint(place.x - 1, place.y - 1);
        }

        public static MPoint UpRight(this MPoint place)
        {
            return new MPoint(place.x + 1, place.y - 1);
        }

        public static MPoint Up(this MPoint place)
        {
            return new MPoint(place.x, place.y - 1);
        }

        public static MPoint Left(this MPoint place)
        {
            return new MPoint(place.x - 1, place.y);
        }

        public static MPoint Right(this MPoint place)
        {
            return new MPoint(place.x + 1, place.y);
        }

        public static MPoint DownRight(this MPoint place)
        {
            return new MPoint(place.x - 1, place.y + 1);
        }

        public static MPoint DownLeft(this MPoint place)
        {
            return new MPoint(place.x + 1, place.y + 1);
        }

        public static MPoint Down(this MPoint place)
        {
            return new MPoint(place.x, place.y + 1);
        }
    } 
    #endregion
}
